/*
 * CategoryClassificationDoubleCheck is a software package that uses 
 * data mining to verify that descriptions of items are classified 
 * into the correct categories.
 * 
 * Copyright (C) 2011  CommuniPatient, LLC
 * 
 * File: ClassificationValidation.java
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *  
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.unitvectory.categoryclassification;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import weka.classifiers.bayes.NaiveBayes;
import weka.core.Attribute;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.converters.TextDirectoryLoader;
import weka.filters.Filter;
import weka.filters.unsupervised.attribute.StringToWordVector;

/**
 * Performs classification using WEKA.
 * 
 * @author Jared Hatfield
 * 
 */
public class ClassificationValidation {

    /**
     * Runs the classification validation on the contents of the specified
     * folder.
     * 
     * <br />
     * <br />
     * 
     * The positive and negative weights are designed to be used to reduce the
     * confidence of the non-classified items. The design is such that a large
     * positive value and a small negative value will provide more suggestions
     * because the possibility of a misclassified item increases.
     * 
     * @param loc
     *            The location of the folder.
     * @param positive
     *            The weight assigned to classified items.
     * @param negative
     *            The weight assigned to non-classified items.
     * @return The results.
     */
    public static List<String> run(File loc, double positive, double negative) {
        List<String> misclassified = new ArrayList<String>();
        try {
            // Convert the directory into a dataset
            TextDirectoryLoader loader = new TextDirectoryLoader();
            loader.setDirectory(loc);
            loader.setOutputFilename(true);
            Instances dataRaw = loader.getDataSet();

            // Apply the StringToWordVector
            StringToWordVector filter = new StringToWordVector();
            filter.setInputFormat(dataRaw);
            Instances dataFiltered = Filter.useFilter(dataRaw, filter);

            // Apply the specified weights to the data
            for (int i = 0; i < dataFiltered.numInstances(); i++) {
                Instance inst = dataFiltered.instance(i);
                Attribute att = inst.classAttribute();
                String cla = att.value((int) inst.classValue());
                if (cla.equals("in")) {
                    // The item was classified
                    inst.setWeight(positive);
                } else {
                    // The item was not classified
                    inst.setWeight(negative);
                }
            }

            // Train the NaiveBayes model
            NaiveBayes classifier = new NaiveBayes();
            classifier.buildClassifier(dataFiltered);

            for (int i = 0; i < dataFiltered.numInstances(); i++) {
                Instance inst = dataFiltered.instance(i);

                double classified = classifier.classifyInstance(inst);
                double actual = inst.classValue();

                if (classified != actual) {
                    Instance stringInstance = dataRaw.instance(i);
                    double[] nums = classifier.distributionForInstance(inst);

                    // Save the filename for the misclassified element
                    for (int j = 0; j < stringInstance.numAttributes(); j++) {
                        Attribute att = stringInstance.attribute(j);
                        if (att.name().equals("filename") && att.isString()) {
                            String val = stringInstance.stringValue(att);
                            val += ":" + nums[(int) classified];
                            misclassified.add(val);
                            break;
                        }
                    }
                }
            }
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }

        return misclassified;
    }
}
